﻿
namespace EasyNet.FastDFS
{
    using System;
    using System.IO;
    using System.Text;

    /// <summary>
    /// FastDFS协议
    /// </summary>
    class Protocol
    {
        public const byte FDFS_PROTO_CMD_QUIT = 82;
        public const byte TRACKER_PROTO_CMD_SERVER_LIST_GROUP = 91;
        public const byte TRACKER_PROTO_CMD_SERVER_LIST_STORAGE = 92;
        public const byte TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE = 93;

        public const byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE = 101;
        public const byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE = 102;
        public const byte TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE = 103;
        public const byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE = 104;
        public const byte TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL = 105;
        public const byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL = 106;
        public const byte TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL = 107;
        public const byte TRACKER_PROTO_CMD_RESP = 100;
        public const byte FDFS_PROTO_CMD_ACTIVE_TEST = 111;
        public const byte STORAGE_PROTO_CMD_UPLOAD_FILE = 11;
        public const byte STORAGE_PROTO_CMD_DELETE_FILE = 12;
        public const byte STORAGE_PROTO_CMD_SET_METADATA = 13;
        public const byte STORAGE_PROTO_CMD_DOWNLOAD_FILE = 14;
        public const byte STORAGE_PROTO_CMD_GET_METADATA = 15;
        public const byte STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE = 21;
        public const byte STORAGE_PROTO_CMD_QUERY_FILE_INFO = 22;
        public const byte STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE = 23;  //create appender file
        public const byte STORAGE_PROTO_CMD_APPEND_FILE = 24;  //append file

        public const byte STORAGE_PROTO_CMD_RESP = TRACKER_PROTO_CMD_RESP;

        public const byte FDFS_STORAGE_STATUS_INIT = 0;
        public const byte FDFS_STORAGE_STATUS_WAIT_SYNC = 1;
        public const byte FDFS_STORAGE_STATUS_SYNCING = 2;
        public const byte FDFS_STORAGE_STATUS_IP_CHANGED = 3;
        public const byte FDFS_STORAGE_STATUS_DELETED = 4;
        public const byte FDFS_STORAGE_STATUS_OFFLINE = 5;
        public const byte FDFS_STORAGE_STATUS_ONLINE = 6;
        public const byte FDFS_STORAGE_STATUS_ACTIVE = 7;
        public const byte FDFS_STORAGE_STATUS_NONE = 99;

        /**
        * for overwrite all old metadata
        */
        public const byte STORAGE_SET_METADATA_FLAG_OVERWRITE = (byte)'O';

        /**
        * for replace, insert when the meta item not exist, otherwise update it
        */
        public const byte STORAGE_SET_METADATA_FLAG_MERGE = (byte)'M';

        public const int FDFS_PROTO_PKG_LEN_SIZE = 8;
        public const int FDFS_PROTO_CMD_SIZE = 1;
        public const int FDFS_GROUP_NAME_MAX_LEN = 16;
        public const int FDFS_IPADDR_SIZE = 16;
        public const int FDFS_DOMAIN_NAME_MAX_SIZE = 128;
        public const int FDFS_VERSION_SIZE = 6;

        public const string FDFS_RECORD_SEPERATOR = "\u0001";
        public const string FDFS_FIELD_SEPERATOR = "\u0002";

        public const int TRACKER_QUERY_STORAGE_FETCH_BODY_LEN = FDFS_GROUP_NAME_MAX_LEN
                            + FDFS_IPADDR_SIZE - 1 + FDFS_PROTO_PKG_LEN_SIZE;
        public const int TRACKER_QUERY_STORAGE_STORE_BODY_LEN = FDFS_GROUP_NAME_MAX_LEN
                            + FDFS_IPADDR_SIZE + FDFS_PROTO_PKG_LEN_SIZE;

        protected const int PROTO_HEADER_CMD_INDEX = FDFS_PROTO_PKG_LEN_SIZE;
        protected const int PROTO_HEADER_STATUS_INDEX = FDFS_PROTO_PKG_LEN_SIZE + 1;

        public const byte FDFS_FILE_EXT_NAME_MAX_LEN = 6;
        public const byte FDFS_FILE_PREFIX_MAX_LEN = 16;
        public const byte FDFS_FILE_PATH_LEN = 10;
        public const byte FDFS_FILENAME_BASE64_LENGTH = 27;

        public const byte ERR_NO_ENOENT = 2;
        public const byte ERR_NO_EIO = 5;
        public const byte ERR_NO_EBUSY = 16;
        public const byte ERR_NO_EINVAL = 22;
        public const byte ERR_NO_ENOSPC = 28;
        public const byte ECONNREFUSED = 61;
        public const byte ERR_NO_EALREADY = 114;

        public const long INFINITE_FILE_SIZE = 256 * 1024L * 1024 * 1024 * 1024 * 1024L;

        private static readonly Encoding encoding = Encoding.GetEncoding("ISO8859-1");

        public static Encoding Encoding { get { return encoding; } }

        public static string GetStorageStatusCaption(byte status)
        {
            switch (status)
            {
                case FDFS_STORAGE_STATUS_INIT:
                    return "INIT";
                case FDFS_STORAGE_STATUS_WAIT_SYNC:
                    return "WAIT_SYNC";
                case FDFS_STORAGE_STATUS_SYNCING:
                    return "SYNCING";
                case FDFS_STORAGE_STATUS_IP_CHANGED:
                    return "IP_CHANGED";
                case FDFS_STORAGE_STATUS_DELETED:
                    return "DELETED";
                case FDFS_STORAGE_STATUS_OFFLINE:
                    return "OFFLINE";
                case FDFS_STORAGE_STATUS_ONLINE:
                    return "ONLINE";
                case FDFS_STORAGE_STATUS_ACTIVE:
                    return "ACTIVE";
                case FDFS_STORAGE_STATUS_NONE:
                    return "NONE";
                default:
                    return "UNKOWN";
            }
        }

        public static byte[] PackHeader(byte cmd, long packageLen, byte errno)
        {
            byte[] header;
            byte[] hex_len;

            header = new byte[FDFS_PROTO_PKG_LEN_SIZE + 2];

            for (int i = 0; i < header.Length; i++)
            {
                header[i] = (byte)0;
            }

            hex_len = ConvertLongToBuff(packageLen);
            Array.Copy(hex_len, 0, header, 0, hex_len.Length);
            header[PROTO_HEADER_CMD_INDEX] = cmd;
            header[PROTO_HEADER_STATUS_INDEX] = errno;

            return header;
        }

        public static ReceivedHeader PackHeader(Stream stream, byte cmd, long bodyLen)
        {
            byte[] header;
            int bytes;
            long pkg_len;

            header = new byte[FDFS_PROTO_PKG_LEN_SIZE + 2];

            if ((bytes = stream.Read(header, 0, header.Length)) != header.Length)
            {
                throw new IOException("recv package size " + bytes + " != " + header.Length);
            }

            if (header[PROTO_HEADER_CMD_INDEX] != cmd)
            {
                throw new IOException("recv cmd: " + header[PROTO_HEADER_CMD_INDEX] + " is not correct, expect cmd: " + cmd);
            }

            if (header[PROTO_HEADER_STATUS_INDEX] != 0)
            {
                return new ReceivedHeader(header[PROTO_HEADER_STATUS_INDEX], 0);
            }

            pkg_len = ConvertBuffToLong(header, 0);
            if (pkg_len < 0)
            {
                throw new IOException("recv body length: " + pkg_len + " < 0!");
            }

            if (bodyLen >= 0 && pkg_len != bodyLen)
            {
                throw new IOException("recv body length: " + pkg_len + " is not correct, expect length: " + bodyLen);
            }

            return new ReceivedHeader((byte)0, pkg_len);
        }

        public static ReceivedPackage PackPackage(Stream stream, byte cmd, long bodyLen)
        {
            var header = PackHeader(stream, cmd, bodyLen);
            if (header.ErrorCode != 0)
            {
                return new ReceivedPackage(header.ErrorCode, null);
            }

            byte[] body = new byte[(int)header.BodyLength];
            int totalBytes = 0;
            int remainBytes = (int)header.BodyLength;
            int bytes;

            while (totalBytes < header.BodyLength)
            {
                if ((bytes = stream.Read(body, totalBytes, remainBytes)) < 0)
                {
                    break;
                }

                totalBytes += bytes;
                remainBytes -= bytes;
            }

            if (totalBytes != header.BodyLength)
            {
                throw new IOException("recv package size " + totalBytes + " != " + header.BodyLength);
            }

            return new ReceivedPackage((byte)0, body);
        }

        public static byte[] ConvertLongToBuff(long n)
        {
            byte[] bs = new byte[8];

            bs[0] = (byte)((n >> 56) & 0xFF);
            bs[1] = (byte)((n >> 48) & 0xFF);
            bs[2] = (byte)((n >> 40) & 0xFF);
            bs[3] = (byte)((n >> 32) & 0xFF);
            bs[4] = (byte)((n >> 24) & 0xFF);
            bs[5] = (byte)((n >> 16) & 0xFF);
            bs[6] = (byte)((n >> 8) & 0xFF);
            bs[7] = (byte)(n & 0xFF);

            return bs;
        }

        public static long ConvertBuffToLong(byte[] bs, int offset)
        {
            return (((long)(bs[offset] >= 0 ? bs[offset] : 256 + bs[offset])) << 56) |
                    (((long)(bs[offset + 1] >= 0 ? bs[offset + 1] : 256 + bs[offset + 1])) << 48) |
                    (((long)(bs[offset + 2] >= 0 ? bs[offset + 2] : 256 + bs[offset + 2])) << 40) |
                    (((long)(bs[offset + 3] >= 0 ? bs[offset + 3] : 256 + bs[offset + 3])) << 32) |
                    (((long)(bs[offset + 4] >= 0 ? bs[offset + 4] : 256 + bs[offset + 4])) << 24) |
                    (((long)(bs[offset + 5] >= 0 ? bs[offset + 5] : 256 + bs[offset + 5])) << 16) |
                    (((long)(bs[offset + 6] >= 0 ? bs[offset + 6] : 256 + bs[offset + 6])) << 8) |
                     ((long)(bs[offset + 7] >= 0 ? bs[offset + 7] : 256 + bs[offset + 7]));
        }
    }
}
